﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using Av.Utils;

namespace JrSnifTest
{

    public class ListenerEventArgs : EventArgs
    {
        public string Msg { get; set; }
    }

    public class Listener
    {

        public IPacketMatcher PacketFilter { get; set; }

        public bool IsListening { get { return m_isListening; } }

        /// <summary>
        /// Whether it should listen to interface
        /// </summary>
        protected bool m_isListening = false;

        /// <summary>
        /// Listening (sniffing :) socket
        /// </summary>
        protected Socket m_socket = null;

        /// <summary>
        /// Interface we are sniffing
        /// </summary>
        protected IPEndPoint m_ep = null;

        /// <summary>
        /// Fires event when new data is captured (sniffed)
        /// </summary>
        public event MyNotificationHandler NewDataCaptured = null;

        public Listener(string ipToListen)
        {
            Log4cs.Log("Going to create socket to listen to {0}...", ipToListen);
            m_ep = new IPEndPoint(IPAddress.Parse(ipToListen), 0);
        }

        public void Start()
        {
            OnNewDataCaptured("Starting listener...");

            Log4cs.Log(PacketFilter.ToString());
            //throw new Exception("STOP IT!!!");

            m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
            m_socket.Bind(m_ep);
            m_socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);
            byte[] byTrue = new byte[4] { 1, 0, 0, 0 };
            byte[] byOut = new byte[4];

            byte[] ba = new byte[4096];

            m_socket.IOControl(IOControlCode.ReceiveAll, byTrue, byOut);
            m_socket.BeginReceive(ba, 0, ba.Length, SocketFlags.None, new AsyncCallback(OnSocketReceived), ba);
            OnNewDataCaptured("Sniffing interface " + m_ep.ToString());
            m_isListening = true;
        }

        public void Stop()
        {
            m_isListening = false;
        }

        public void OnSocketReceived(IAsyncResult ar)
        {
            try
            {
                byte[] ba = (byte[])ar.AsyncState;
                AnalizeReceivedPacket(ba);

            } catch(Exception ex)
            {
                Log4cs.Log(Importance.Error, "Error receiving sniffed data!");
                Log4cs.Log(Importance.Debug, ex.ToString());
                OnNewDataCaptured("Error receiving sniffed data!");
            } finally
            {
                if( m_isListening )
                {
                    byte[] ba = new byte[4096];
                    m_socket.BeginReceive(ba, 0, ba.Length, SocketFlags.None, new AsyncCallback(OnSocketReceived), ba);
                }
            }
        }

        protected int m_droppedPackets = 0;
        protected int m_droppedProtocols = 0;

        /// <summary>
        /// Analizes IP packet using user specified filter and fires event in case packet fits
        /// </summary>
        /// <param name="ba">Received packed (byte [])</param>
        private void AnalizeReceivedPacket(byte[] ba)
        {
            // Check IP protocol, it is fast and w/o IP packet parsing
            if(PacketFilter.IpProtocolMatch(ba))
            {
                IpHeader header = IpHeader.Parse(ba);
                if(PacketFilter.Match(header))
                {
                    OnNewDataCaptured(header);
                } else
                {
                    if(++m_droppedPackets % 100 == 0)
                    {
                        OnNewDataCaptured(m_droppedPackets + " packets do not match rules");
                    }
                }
            } else
            {
                if(++m_droppedProtocols % 100 == 0)
                {
                    OnNewDataCaptured(m_droppedProtocols + " packets do not match protocol");
                }
            }
        }

        /// <summary>
        /// Fires event if there are subscibers for NewDataCaptured event
        /// </summary>
        protected void OnNewDataCaptured(IpHeader header)
        {
            string msg = "";
            if(header.ProtocolName == "TCP")
            {
                msg = string.Format("<{0}:{1}> -> <{2}:{3}> ({4})", header.SourceIp, header.TcpHeader.SourcePort, header.DestinationIp, header.TcpHeader.DestinationPort, header.ProtocolName);
            } else
            {
                msg = string.Format("<{0}> -> <{1}> ({2})", header.SourceIp, header.DestinationIp, header.ProtocolName);
            }
            OnNewDataCaptured(msg);
        }

        protected void OnNewDataCaptured(string msg)
        {
            if(NewDataCaptured != null)
            {
                ListenerEventArgs e = new ListenerEventArgs();
                e.Msg = msg;
                NewDataCaptured("Listener", e);
            }
        }

    }
}
